#region License, Terms and Conditions
//
// Prawks - Copyright (c) 2007 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Prawks.CodeProvisioning
{
    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    
    using Hashtable = System.Collections.Hashtable;
    using ReaderWriterLock = System.Threading.ReaderWriterLock;
    using Timeout = System.Threading.Timeout;
    using HybridDictionary = System.Collections.Specialized.HybridDictionary;

    /// <summary>
    /// Provides dynamic instantiation of concrete types for abstract base classes 
    /// that contain methods bound to externally provided implementations.
    /// This class can neither be inherited nor instantiated.
    /// </summary>
    /// <remarks>
    /// Any public static (<b>Shared</b> in Visual Basic) members of this type are 
    /// safe for multithreaded operations.
    /// </remarks>
    
    public sealed class DynamicTypeImpl
    {
        private static readonly Hashtable _bakedTypes = new Hashtable();
        private static readonly HybridDictionary _packages = new HybridDictionary();
        private static readonly ReaderWriterLock _lock = new ReaderWriterLock();
        private static int _uniqueCounter = 0;

        private const string DynamicAssemblyNamePrefix = "PrawksCP";

        private DynamicTypeImpl()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Creates an instance of the concrete type for a base class.
        /// </summary>
        /// <param name="baseType">
        /// The abstract base class whose concrete type should be instantiated.
        /// </param>
        /// <returns>
        /// Returns an instance of the new concrete type.
        /// </returns>

        public static object CreateInstance(Type baseType)
        {
            return CreateInstance(baseType, null);
        }

        /// <summary>
        /// Creates an instance of the concrete type for a base class using the 
        /// constructor that best matches the specified parameters.
        /// </summary>
        /// <param name="baseType">
        /// The abstract base class whose concrete type should be instantiated.
        /// </param>
        /// <param name="args">
        /// An array of arguments that match in number, order, and type the parameters 
        /// of the constructor to invoke. If this is an empty array or a null reference 
        /// (<b>Nothing</b> in Visual Basic), the constructor that takes no parameters 
        /// (the default constructor) is invoked. 
        /// </param>
        /// <returns>
        /// Returns an instance of the new concrete type.
        /// </returns>

        public static object CreateInstance(Type baseType, object[] args)
        {
            //
            // Validate parameters
            //

            if (baseType == null)
                throw new ArgumentNullException("baseType");

            //
            // Get the concrete type that holds the full implementation of
            // the base type and return a new instance of it.
            //

            return Activator.CreateInstance(GetImplType(baseType), args);
        }

        /// <summary>
        /// Gets the concrete implementation type for a given abstract base class.
        /// </summary>
        /// <param name="baseType">
        /// The abstract base class for which to return the concrete implementation type.
        /// </param>
        /// <returns>
        /// Returns the concrete implementation type.
        /// </returns>

        public static Type GetImplType(Type baseType)
        {
            //
            // Validate parameters
            //

            if (baseType == null)
                throw new ArgumentNullException("baseType");

            //
            // Get the implementation of given base type. If none exists
            // then one needs to baked.
            //

            Type bakedType;

            _lock.AcquireReaderLock(Timeout.Infinite);

            try
            {
                bakedType = (Type) _bakedTypes[baseType];

                if (bakedType == null)
                {
                    _lock.UpgradeToWriterLock(Timeout.Infinite);

                    //
                    // Check again after writer lock has been acquired for
                    // the remote case where a writer on another thread 
                    // manages to bake the same type first.
                    //

                    bakedType = (Type) _bakedTypes[baseType];
                
                    if (bakedType == null)
                    {
                        //
                        // Check if a dynamic assembly/module (package combo)
                        // has been created previously to hold the implementation
                        // of the new type. If not, a new one is created and 
                        // remembered. There is a one-to-one mapping between
                        // between base assemblies and their dynamic 
                        // counterparts for some safety and isolation purposes.
                        //

                        Assembly baseAssembly = baseType.Assembly;
                        DynamicPackage package = (DynamicPackage) _packages[baseAssembly];
                        
                        if (package == null)
                        {
                            package = new DynamicPackage(baseAssembly);
                            _packages.Add(baseAssembly, package);
                        }

                        //
                        // The implementation has not been created yet, so bake it
                        // by compiling a new type that inherits from the supplied
                        // base type. Type names containing special characters
                        // are escaped with a backslash (\) character, which is
                        // important if the type is nested.
                        //

                        string newTypeName = TypeNameMangler.Default(baseType.FullName);
                        bakedType = TypeImplCompiler.Compile(baseType, newTypeName, package.Module);
                        _bakedTypes.Add(baseType, bakedType);
                    }
                }
            }
            finally
            {
                _lock.ReleaseReaderLock();
            }

            return bakedType;
        }

        /// <summary>
        /// Determines is an object is a synthesized dynamic implementation of
        /// its base type acquired via <see cref="CreateInstance(Type)"/>.
        /// </summary>
        /// <param name="o">Object to test.</param>
        /// <returns>
        /// Returns true if the object is a synthesized dynamic implementation 
        /// of its base type ; otherwise false.
        /// </returns>
        
        public static bool IsImpl(object o)
        {
            if (o == null)
                throw new ArgumentNullException("o");

            return IsImpl(o.GetType());
        }

        /// <summary>
        /// Determines is the type is a synthesized dynamic implementation of
        /// its base type acquired via <see cref="GetImplType"/>.
        /// </summary>
        /// <param name="type">Type to test.</param>
        /// <returns>
        /// Returns true if the type is a synthesized dynamic implementation 
        /// of its base type ; otherwise false.
        /// </returns>

        public static bool IsImpl(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            //
            // TODO: This is not the most efficient way of checking because
            // we take a very coarse-grained lock here that may create some
            // contention with the GetImplType method. This method is not
            // expected to be used in most common scenarios, except perhaps
            // for occasional diagnostics. Therefore, safety is guaranteed
            // but efficiency is not optimal. This can be fixed in a future
            // release if the method is found to be used often.
            //

            _lock.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _bakedTypes.ContainsValue(type);
            }
            finally
            {
                _lock.ReleaseReaderLock();
            }
        }

        private sealed class DynamicPackage
        {
            public readonly AssemblyBuilder Assembly;
            public readonly ModuleBuilder Module;

            public DynamicPackage(Assembly baseAssembly)
            {
                //
                // Define a dynamic assembly with Run access only. The assumption 
                // here is that this type will be used in a dynamic environment 
                // exclusively where the assembly will never be persisted to disk.
                //

                AssemblyName newAssemblyName = new AssemblyName();

                newAssemblyName.Name = string.Format("{0}_{1}_{2}_{3}",
                    DynamicAssemblyNamePrefix, baseAssembly.GetName().Name,
                    (_uniqueCounter++).ToString(), 
                    AppDomain.CurrentDomain.GetHashCode().ToString("x"));

                this.Assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(newAssemblyName, AssemblyBuilderAccess.Run);
                Module = this.Assembly.DefineDynamicModule(newAssemblyName.Name + ".netmodule");
            }
        }
    }
}
